package fxification.java;

import com.sun.javafx.functions.Function1;
import com.sun.javafx.functions.Function2;
import com.sun.javafx.runtime.sequence.Sequence;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;

public class FxIO {
    public static void readLines(
            final java.io.InputStream stream,
            final String charset,
            final int buffSize,
            final Function1<Void, ? super String> onReadLine,
            final Function1<Void, ? super Long> onReadLinesDone,
            final Function1<Void, ? super Throwable> onFailure) {

        if(stream == null){
            throw raiseFailure(onFailure, "The InputStream instance cannot be null.");
        }

        if(onReadLine == null){
            throw raiseFailure(onFailure, "You must provide a handler function for readLines().");
        }

        BufferedReader reader = null;
        long counter = 0;
        try {

            reader = new BufferedReader(new InputStreamReader(stream,charset), buffSize);
            
            String line = null;
            while ((line = reader.readLine()) != null) {
                onReadLine.invoke(line);
                counter++;
            }

        }catch (FileNotFoundException ex) {throw raiseFailure(onFailure, ex);
        }catch (IOException ex) {throw raiseFailure(onFailure, ex);
        }catch (Exception ex){throw raiseFailure(onFailure, ex);}

        finally{
            // reading done
            if(onReadLinesDone != null){
                onReadLinesDone.invoke(counter);
            }
        }
    }

    public static void readBytes(
        final InputStream stream,
        final int buffSize,
        final Function2<Void, ? super byte[], ? super Integer> onReadBytes,
        final Function1<Void, ? super Long> onReadBytesDone,
        final Function1<Void, ? super Throwable> onFailure){

        if(stream == null){
            throw raiseFailure(onFailure, "The InputStream instance cannot be null.");
        }
        
        if(onReadBytes == null){
            throw raiseFailure(onFailure, "You must provide a handler function for readBytes().");
        }

        BufferedInputStream reader = null;
        long cursor = 0;
        try{
            reader = new BufferedInputStream(stream,buffSize);
            byte[] buff = new byte[buffSize];
            int bytesRead = 0;

            while((bytesRead = reader.read(buff)) > -1){
                if(onReadBytes != null){
                    onReadBytes.invoke(buff, bytesRead);
                    cursor = cursor + bytesRead;
                }
            }
        }
        catch(FileNotFoundException ex){throw raiseFailure(onFailure, ex);}
        catch(IOException ex){throw raiseFailure(onFailure, ex);}
        catch(Exception ex){throw raiseFailure(onFailure, ex);}

        finally {
            // done reading
            if(onReadBytesDone != null){
                onReadBytesDone.invoke(cursor);
            }
        }
    }

    public static void traverseDirectory(
            final String dir,
            final String filter,
            final Function1<Void, ? super File> onEachFile,
            final Function1<Void, ? super Long> onTraverseDirDone,
            final Function1<Void, ? super Throwable> onFailure) {

        if(onEachFile == null){
            throw raiseFailure(onFailure,"You must provide a handler function to traverse directory.");
        }

        File directory = new File(dir);
        if (directory.isFile()) {
            throw raiseFailure(onFailure, "The specified file cannot be found.");
        }

        long count = 0;
        if (directory.canRead() && onEachFile != null) {
            for (File f : getDirFiles(directory, filter)) {
                onEachFile.invoke(f);
                count++;
            }
        }

        if (onTraverseDirDone != null) {
            onTraverseDirDone.invoke(count);
        }
    }

    public static void doWithInputStream(
            InputStream stream,
            final Function1<Void, ? super java.io.InputStream> streamHandler,
             final Function1<Void, ? super Throwable> onFailure){
        
        if(streamHandler == null){
            throw raiseFailure(onFailure,"You must provide a handler function for InpuStream.");
        }
        
        try {
            if(stream != null){
                streamHandler.invoke(stream);
            }
        }catch(Exception ex){throw raiseFailure(onFailure, ex);}
    }

    public static InputStream getFileInputStream(String fileName){
        InputStream stream = null;
        try {
            stream = getResourceStream(fileName);
        } catch (FileNotFoundException ex) {
            throw new RuntimeException(ex);
        }catch(Exception ex){throw new RuntimeException(ex);}
        
        return stream;
    }

    public static InputStreamReader getFileInputStreamReader(String fileName){
        InputStreamReader stream = null;

        try {
            stream = new InputStreamReader(getResourceStream(fileName));
        } catch (FileNotFoundException ex) {
            throw new RuntimeException(ex);
        }catch(Exception ex){throw new RuntimeException(ex);}

        return stream;
    }
    public static void doWithInputStreamReader(
        InputStreamReader reader,
        final Function1<Void, ? super java.io.InputStreamReader> readerHandler,
        final Function1<Void, ? super Throwable> onFailure){

        if(readerHandler == null){
            throw raiseFailure(onFailure,"You must provide a handler function for InputStreamReader.");
        }
        try {
            if(reader != null){
                readerHandler.invoke(reader);
            }
        }catch(Exception ex){throw raiseFailure(onFailure, ex);}
    }

    public static void writeLines(
            final OutputStream stream,
            final String charset,
            final int bufferSize,
            final Sequence<? extends String> lines,
            final Function1<Void, ? super String> onWriteLine,
            final Function1<Void, ? super Long> onWriteLinesDone,
            final Function1<Void, ? super Throwable> onFailure
    ){
        if(stream == null){
            throw raiseFailure(onFailure, "The OutputStream instance cannot be null.");
        }

        if(lines == null){
           return;
        }

        OutputStreamWriter writer = null;
        long counter = 0;
        try {
            writer = new OutputStreamWriter(new BufferedOutputStream(stream, bufferSize), charset);
            
            for(Iterator<? extends String> it = lines.iterator(); it.hasNext();){
                String line = it.next();
                if(onWriteLine != null){
                    onWriteLine.invoke(line);
                }
                writer.write(line);
                counter++;
            }
        } 
        catch (FileNotFoundException ex) {throw raiseFailure(onFailure, ex);}
        catch(Exception ex){throw raiseFailure(onFailure, ex);}
        finally{

            if(onWriteLinesDone != null){
                onWriteLinesDone.invoke(counter);
            }
        }
    }

    public static void writeBytes(
            final OutputStream stream,
            final int buffSize,
            final byte[] bytes,
            final Function1<Void, ? super byte[]> onWriteBytes,
            final Function1<Void, ? super Long> onWriteBytesDone,
            final Function1<Void, ? super Throwable> onFailure

    ){
        if(stream == null){
            throw raiseFailure(onFailure, "The OutputStream instance cannot be null.");
        }
        long counter = 0;
        BufferedOutputStream writer = null;
        try {
            writer = new BufferedOutputStream(stream, buffSize);
            if(onWriteBytes != null){
                onWriteBytes.invoke(bytes);
            }
            writer.write(bytes);
            counter = bytes.length;
        }
        catch (FileNotFoundException ex) {throw raiseFailure(onFailure, ex);}
        catch(IOException ex){throw raiseFailure(onFailure, ex);}
        finally{
            if(onWriteBytesDone != null){
                onWriteBytesDone.invoke(counter);
            }
        }
    }

    public static void doWithOutputStream (
            OutputStream stream,
            final Function1<Void, ? super java.io.OutputStream> streamHandler,
            final Function1<Void, ? super Throwable> onFailure
    ){
        if(streamHandler == null){
            throw raiseFailure(onFailure,"You must provide a handler function for OutputStream.");
        }
        try {
            if(stream != null){
                streamHandler.invoke(stream);
            }
        }catch(Exception ex){throw raiseFailure(onFailure, ex);}
    }


    public static void doWithOutputStreamWriter (
            OutputStreamWriter stream,
            final Function1<Void, ? super java.io.OutputStreamWriter> streamHandler,
            final Function1<Void, ? super Throwable> onFailure
    ){
        if(streamHandler == null){
            throw raiseFailure(onFailure,"You must provide a handler function for OutputStreamWriter.");
        }
        try {
            if(stream != null){
                streamHandler.invoke(stream);
            }
        }catch(Exception ex){throw raiseFailure(onFailure, ex);}
    }

    public static void doWithPrintWriter (
            PrintWriter writer,
            final Function1<Void, ? super java.io.PrintWriter> writerHandler,
            final Function1<Void, ? super Throwable> onFailure
    ){
        if(writerHandler == null){
            throw raiseFailure(onFailure,"You must provide a handler function for PrintWriter.");
        }
        try {
            if(writer != null){
                writerHandler.invoke(writer);
            }
        }catch(Exception ex){throw raiseFailure(onFailure, ex);}
    }
    
    public static OutputStream getFileOutputStream (String fileName){
        FileOutputStream stream = null;
        try {
            stream =  new FileOutputStream(new File(fileName));
        } catch (FileNotFoundException ex) {
            throw new RuntimeException(ex);
        }
        return stream;
    }
    
    public static OutputStreamWriter getFileOutputStreamWriter (String fileName){
        OutputStreamWriter writer = null;
        try {
            writer = new OutputStreamWriter(new FileOutputStream(fileName));
        } catch (FileNotFoundException ex) {
            throw new RuntimeException (ex);
        }
        return writer;
    }
    
    public static PrintWriter getFilePrintWriter (String fileName){
        PrintWriter writer = null;
        try {
            writer = new PrintWriter(new File(fileName));
        } catch (FileNotFoundException ex) {
            throw new RuntimeException (ex);
        }
        return writer;
    }

    public static void closeInputStream(InputStream s){
        if(s != null){
            try {
                s.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public static void closeReader (java.io.Reader r){
        if(r != null){
            try {
                r.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public static void closeOutputStream (OutputStream o){
        if(o != null){
            try {
                o.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public static void closeWriter (java.io.Writer w){
        if (w != null){
            try {
                w.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    private static InputStream getResourceStream(String url) throws Exception{
        // this is a jar-enclosed resource
        InputStream stream = null;
        try{
            if(url.contains("jar:file")){
                String[] urlParts = url.split("!");
                URLClassLoader cl = new URLClassLoader(new URL[]{new URL(urlParts[0]+"!/")});
                stream = cl.getResourceAsStream(urlParts[1]);
            }else{
                stream = new FileInputStream(url);
            }
        }catch(MalformedURLException ex){
            throw new Exception ("Unable to load file: " + ex.getMessage());
        }
        catch(FileNotFoundException ex){
            throw new Exception ("Unable to load file: " + ex.getMessage() );
        }
        return stream;
    }

    private static File[] getDirFiles(final File dir, final String filter){
        if(filter != null && filter.length() > 0){
            FileFilter fileFilter = new FileFilter() {
                @Override
                public boolean accept(File fileName) {
                    return fileName.toString().matches(filter);
                }
            };
           return dir.listFiles(fileFilter);
        }
        return dir.listFiles();
    }

    private static void invokeFailureHandler (Function1<Void, ? super Throwable> onFailure, Throwable ex){
       if(onFailure != null){
           onFailure.invoke(ex);
       }
    }

    private static RuntimeException raiseFailure(Function1<Void, ? super Throwable> failureHandler, String msg){
        RuntimeException ex = new RuntimeException(msg);
        invokeFailureHandler(failureHandler, ex);
        return ex;
    }

    private static RuntimeException raiseFailure(Function1<Void, ? super Throwable> failureHandler, Throwable ex){
        invokeFailureHandler(failureHandler, ex);
        return new RuntimeException(ex);
    }
}
